package net.oschina.bilbodai.common.beanutil.reflection;

import com.google.common.base.Preconditions;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

/**
 * @author daimao  Date: 2016/12/27 Time: 下午2:21
 * @version $Id$
 */
public class Reflection {

    private String basePath = "";

    public Reflection() {
    }

    public Reflection(String basePath) {
        this.basePath = basePath;
    }

    public void scanFields(final Class<?> type, final FieldCallback callback) {
        Preconditions.checkNotNull(callback);
        StringBuilder nodePath = new StringBuilder(basePath);
        Class<?> currentType = type;
        while (currentType != Object.class) {
            scanCurrentType(currentType, callback, nodePath);
            currentType = currentType.getSuperclass();
        }
    }

    private void scanCurrentType(Class<?> type, FieldCallback callback, StringBuilder nodePath) {
        Field[] declaredFields = type.getDeclaredFields();
        Set<Class<?>> visitedTypes = new HashSet<>();
        visitedTypes.add(type);
        int nodePathLen = nodePath.length();
        for (Field declaredField : declaredFields) {
            scanFields(declaredField, callback, nodePath, visitedTypes);
            nodePath.setLength(nodePathLen);
        }
    }

    private void scanFields(final Field field,
            final FieldCallback callback,
            final StringBuilder nodePath,
            Set<Class<?>> visitedTypes) {

        if (nodePath.length() > 0) {
            nodePath.append(".");
        }
        final String superPath = nodePath.toString();
        nodePath.append(field.getName());

        Class<?> type = field.getType();
        if (isJdkType(type) && callback.match(field, type)) {
            callback.onFieldFound(field, type, superPath, nodePath.toString());
        }

        if (isJdkType(type))
            return;
        visitedTypes.add(type);
        int nodePathLength = nodePath.length();

        Field[] declaredFields = type.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            scanFields(declaredField, callback, nodePath, visitedTypes);
            nodePath.setLength(nodePathLength);
        }
    }

    public static boolean isJdkType(Class<?> type) {
        return type.isPrimitive() || type.getName().startsWith("java");
    }
}